package com.azunitech.apm.bundles.shell;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class TelnetBinding<T> implements Runnable, Binding 
{
	private final ServerSocket m_socket;
	private final ICommand<T> m_command;
	private final int m_maxConnections;
	private final List<StoppableShellThread> m_threads = new ArrayList<StoppableShellThread>();
	private Thread m_thread = new Thread(this);

	public TelnetBinding( ICommand<T> command, ServerSocket socket, int maxConnections) 
	{
		m_command = command;
		m_socket = socket;
		m_maxConnections = maxConnections;
	}

	public void run() 
	{
		while (!Thread.interrupted()) 
		{
			final Socket socket;
			try 
			{
				socket = m_socket.accept();
			} 
			catch (IOException ex) 
			{
				if (Thread.interrupted()) 
				{
					break;
				}
				throw new RuntimeException(ex);
			}

			synchronized (m_threads) 
			{
				if (m_threads.size() >= m_maxConnections) 
				{
					try 
					{
						socket.close();
					} 
					catch (IOException e) 
					{
						// Ignore
					}
					continue;
				}
			}
			Shell<T> shell;
			try 
			{
				shell = new Shell<T>(m_command, new BufferedReader(new InputStreamReader(socket.getInputStream())),
						new PrintStream(socket.getOutputStream()), new PrintStream(socket.getOutputStream()));
			} 
			catch (IOException e) 
			{
				e.printStackTrace();
				try 
				{
					socket.close();
				} 
				catch (IOException ex) 
				{
					// Ignore
				}
				continue;
			}

			StoppableShellThread thread = new StoppableShellThread(shell, socket);

			thread.start();
		}

		List<StoppableShellThread> threads;

		synchronized (m_threads) 
		{
			threads = new ArrayList<StoppableShellThread>(m_threads);
		}

		for (StoppableShellThread thread : threads) 
		{
			try 
			{
				thread.stopAndWait();
			} 
			catch (InterruptedException e) 
			{
				// Ignore
			}
		}
		Thread.currentThread().interrupt();
	}

	public void start() 
	{
		m_thread.start();
	}

	public void stop() throws InterruptedException 
	{
		m_thread.interrupt();
		try 
		{
			m_socket.close();
		} 
		catch (IOException e) 
		{
			// Ignore
		}
		m_thread.join();
	}

	private final class StoppableShellThread extends Thread 
	{
		private final Socket m_socket;

		public StoppableShellThread(Shell<T> shell, Socket socket) 
		{
			super(shell);
			m_socket = socket;
		}

		public void start() 
		{
			synchronized (m_threads) 
			{
				m_threads.add(this);
			}

			super.start();
		}

		void stopAndWait() throws InterruptedException 
		{
			if (!isAlive()) 
			{
				return;
			}
			interrupt();
			
			try 
			{
				m_socket.close();
			} 
			catch (IOException e) 
			{
				// Ignore
			}
			join();
		}

		public void run() 
		{
			try 
			{
				super.run();
			} 
			finally 
			{
				synchronized (m_threads) 
				{
					m_threads.remove(this);
				}
			}
		}
	}
}
